package com.solo.common.orm.core.query;

import cn.hutool.core.text.NamingCase;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.table.TableDef;
import com.solo.common.core.consts.Order;
import com.solo.common.core.utils.AnnotationUtils;
import com.solo.common.core.utils.ObjectUtils;
import com.solo.common.core.utils.ServletUtils;
import com.solo.common.core.utils.StringUtils;
import com.solo.common.orm.core.query.anno.Query;
import com.solo.common.orm.core.query.anno.QueryTable;
import com.solo.common.orm.core.query.enums.ColumnStyle;
import com.solo.common.orm.core.query.enums.Connector;
import com.solo.common.orm.exception.NotFoundAnnoException;
import com.solo.satoken.model.LoginUser;
import com.solo.satoken.utils.LoginHelper;
import com.solo.system.api.consts.role.DataScope;
import com.solo.system.api.entity.SysRole;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import static com.mybatisflex.core.query.QueryMethods.bracket;

/**
 * 自定义查询条件包装工具类
 * @author 十一
 * @since 2023/09/19 10:24
 * 人生若只如初见，何事秋风悲画扇
 **/
public class Wrappers {

    public static QueryWrapper builder(Object entity) {
        return builder(null, entity);
    }

    /**
     * 构建查询条件包装器
     * @param queryWrapper 查询条件
     * @param entity 查询实体
     * @return 查询条件包装器
     */
    public static QueryWrapper builder(QueryWrapper queryWrapper, Object entity) {
        Class<?> clas = entity.getClass();
        QueryTable queryTable = AnnotationUtils.getAnnotation(clas, QueryTable.class);
        if (ObjectUtils.isNull(queryTable)) {
            throw new NotFoundAnnoException("orm", String.format("在类[%s], 未找到 @QueryTable 注解", clas.getName()));
        }
        if (ObjectUtils.isNull(queryWrapper)) {
            queryWrapper = QueryWrapper.create();
        }
        String alias = queryTable.alias();
        String tableName = getTableName(queryTable.table());
        String columnTableName = tableName;
        // 如果有别名，则设置表的别名
        if (StringUtils.isNotBlank(alias)) {
            queryWrapper.from(new com.mybatisflex.core.query.QueryTable(tableName).as(alias));
            columnTableName = alias;
        }

        Map<Integer, QueryCondition> map = new HashMap<>();
        
        Field[] fields = ReflectUtil.getFields(clas);
        for (Field field : fields) {
            Object fieldValue = ReflectUtil.getFieldValue(entity, field);
            Query query = AnnotationUtils.getAnnotation(field, Query.class);
            if (ObjectUtils.isEmpty(fieldValue) || ObjectUtils.isNull(query)) continue;
            int group = query.group();
            // 如果指定了@Query注解的value,则直接使用value作为列名，否则默认使用字段名转@Wrappers style风格作为列名
            String columnName = StringUtils.isNotBlank(query.value()) ? query.value() : toColumnName(field.getName(), queryTable.style());
            Class<? extends TableDef>[] columnTable = query.table();

            // 如果设置了表别名，则优先使用别名，否则使用表名
            if (StringUtils.isNotBlank(query.alias())) {
                columnTableName = query.alias();
            } else if (ArrayUtil.isNotEmpty(columnTable)) {
                columnTableName = getTableName(columnTable[0]);
            }
            QueryColumn column = new QueryColumn(columnTableName, columnName);
            QueryCondition condition = initCondition(query, column, fieldValue);
            if (group > 0) {
                // 如果是相同组的查询条件，则先跳过，循环完毕之后，统一用圆括号包括起来
                QueryCondition queryCondition = map.get(group);
                if (ObjectUtils.isNull(queryCondition)) {
                    queryCondition = QueryCondition.createEmpty();
                }
                if (query.connector().equals(Connector.AND)) {
                    queryCondition.and(condition);
                } else {
                    queryCondition.or(condition);
                }
                map.put(group, queryCondition);
                continue;
            }
            if (query.connector().equals(Connector.AND)) {
                queryWrapper.and(condition);
            } else {
                queryWrapper.or(condition);
            }
        }
        // 设置相同组的查询条件外边用圆括号包括起来
        for (Map.Entry<Integer, QueryCondition> entry : map.entrySet()) {
            // TODO 这里有一个问题，只能用and连接，无法用or连接
            queryWrapper.and(bracket(entry.getValue()));
        }
        
        // 当表别名未指定时，使用表名
        String tableAlias = StringUtils.blankToDefault(alias, tableName);
        // 处理排序
        handleSortChange(queryWrapper, tableAlias, queryTable);
        if (queryTable.dataScope()) {
            return dataScope(queryWrapper, queryTable, tableAlias);
        }
        return queryWrapper;
    }

    /**
     * 数据范围
     * @param queryWrapper 查询包装器
     * @param queryTable 查询条件包装
     * @return {@link QueryWrapper }
     */
    public static QueryWrapper dataScope(QueryWrapper queryWrapper, QueryTable queryTable, String tableAlias) {
        QueryCondition queryCondition = QueryCondition.createEmpty();
        LoginUser loginUser = LoginHelper.getLoginUser();
        assert loginUser != null;
        List<SysRole> roles = loginUser.getRoles();
        Long deptId = loginUser.getDeptId();
        Set<Long> dataPermissions = loginUser.getDataPermissions();
        Set<DataScope> roleDataScopes = new HashSet<>();    // 数据范围集合
        roles.forEach(role -> {
            DataScope dataScope = role.getDataScope();
            roleDataScopes.add(dataScope);
        });

        // 1.全部数据权限
        if (roleDataScopes.contains(DataScope.ALL)) {
            return queryWrapper;
        }

        // 若未指定用户表别名，则使用当前表别名
        String userAlias = StringUtils.blankToDefault(queryTable.userAlias(), tableAlias);
        //5.仅本人数据权限
        if (roleDataScopes.contains(DataScope.ONLY_ME)) {
            queryCondition.or(new QueryColumn(userAlias, queryTable.userIdColumn()).eq(loginUser.getUserId()));
        }

        // 若未指定部门表别名，则使用当前表别名
        String deptAlias = StringUtils.blankToDefault(queryTable.deptAlias(), tableAlias);
        // 2.本部门数据权限 / 3.本部门及以下数据权限 / 4.自定义数据权限
        if (dataPermissions.size() > 1) {
            queryCondition.or(new QueryColumn(deptAlias, queryTable.deptIdColumn()).in(deptId));
        } else {
            queryCondition.or(new QueryColumn(deptAlias, queryTable.deptIdColumn()).eq(deptId));
        }
        
        //所有新增的查询条件外边用圆括号包括起来
        queryWrapper.and(bracket(queryCondition));
        return queryWrapper;
    }

    /**
     * 获取表名
     * @return {@link String }
     */
    private static String getTableName(Class<? extends TableDef> table) {
        TableDef tableDefInstance;
        try {
            // 实例化对象
            Constructor<? extends TableDef> constructor = table.getDeclaredConstructor();
            constructor.setAccessible(true); // 确保私有构造函数也可以访问
            tableDefInstance = constructor.newInstance();        
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        // 调用 getTableName() 方法
        return tableDefInstance.getTableName();
    }

    /**
     * 将字段名转换为指定风格的列名
     * 如果需要拓展其他风格，可以在这里添加，并在 {@link ColumnStyle} 中添加枚举值
     * @param fieldName 字段名称
     * @param style     风格
     * @return {@link String}
     */
    private static String toColumnName(String fieldName, ColumnStyle style) {
        return switch (style) {
            case camel_to_underline -> NamingCase.toUnderlineCase(fieldName);
            case PascalCase -> NamingCase.toPascalCase(fieldName);
            default -> fieldName;
        };
    }

    /**
     * 初始化查询条件
     * @param query 查询注解
     * @param column 查询列
     * @param fieldValue 字段值
     * @return 查询条件
     */
    private static QueryCondition initCondition(Query query, QueryColumn column, Object fieldValue) {
        return switch (query.mode()) {
            case NE -> column.ne(fieldValue);
            case GT -> column.gt(fieldValue);
            case GE -> column.ge(fieldValue);
            case LT -> column.lt(fieldValue);
            case LE -> column.le(fieldValue);
            case BETWEEN -> {
                try {
                    Object[] values = (Object[]) fieldValue;
                    yield column.between(values[0], values[1]);
                } catch (Exception e) {
                    // TODO 这里异常优化一下
                    throw new RuntimeException("BETWEEN 模式下，字段值必须为数组");
                }
            }
            case LIKE -> column.like(fieldValue);
            case NOT_LIKE -> column.notLike(fieldValue);
            case LIKE_LEFT -> column.likeLeft(fieldValue);
            case LIKE_RIGHT -> column.likeRight(fieldValue);
            case NOT_LIKE_LEFT -> column.notLikeLeft(fieldValue);
            case NOT_LIKE_RIGHT -> column.notLikeRight(fieldValue);
            case IS_NULL -> column.isNull();
            case IS_NOT_NULL -> column.isNotNull();
            case IN -> column.in(fieldValue);
            case NOT_IN -> column.notIn(fieldValue);
            default -> column.eq(fieldValue);
        };
    }

    /**
     * 处理排序更改
     * @param queryWrapper 查询包装器
     * @param tableAlias 表别名
     * @param queryTable 排序列
     */
    private static void handleSortChange(QueryWrapper queryWrapper, String tableAlias, com.solo.common.orm.core.query.anno.QueryTable queryTable) {
        // 获取前端的排序列和排序方式
        String orderByColumn = ServletUtils.getRequest().getParameter("orderByColumn");
        String order = ServletUtils.getRequest().getParameter("order");
        
        if (StringUtils.isAllNotBlank(orderByColumn, order)){
            // 如果前端传了排序列和者排序方式，则使用前端传过来的排序方式进行排序
            boolean isAsc = StringUtils.equals(Order.ASCENDING, order);
            queryWrapper.orderBy(new QueryColumn(tableAlias, toColumnName(orderByColumn, queryTable.style())), isAsc);
        } else if (StringUtils.isNotBlank(queryTable.orderBy())){
            // 如果后台设置了默认的排序方式，则把该排序方式追加
            queryWrapper.orderBy(new QueryColumn(tableAlias, queryTable.orderBy()), queryTable.asc());
        }
    }

}
